﻿//===================================================================================
// Copyright (c) Ryan Garrett.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================

//WRITE: Make sure to write about the proper way to use the ValidationMethod. Different collection instances with the same validation errors will not 
//       cause a property change. Reusing the same instance of error collection, changing the contents within it, then re-assigning it during a validation method will NOT cause a property change. Discourage doing this
//WRITE: Write about unconstrained values with respect to metadata
//WRITE: Make sure to specify which method calls on the ModelCollection class will cause Validation to happen
//WRITE: Remember in guidance to mention for them NOT to change state during validation, as validation could be called several times.
//DESIGNER: In designer, give users the ability to change the order of registration

using Caffeine.Core.Metadata;
using Caffeine.Core.Test.TestModel;
using Caffeine.Core.Test.TestModel.PropertyRegistrations;
using Caffeine.Core.Test.TestModel.ValidationModels;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace Caffeine.Core.Test
{
    [TestClass]
    public class ModelTester
    {
        [TestMethod]
        public void ModelFactoryTests()
        {
            IModelFactory factory = new ModelFactory();
            ModelA vm = factory.CreateModel<ModelC<String>>();

            ModelForFactoryTesting viewModel = factory.CreateModel<ModelForFactoryTesting>();
            Assert.AreEqual(viewModel.ParentFactory, factory);
            Assert.AreEqual(viewModel.PropertyChangeCounter, factory.PropertyChangeCounter);
            Assert.AreEqual(viewModel.Child.Value.ParentFactory, factory);
            Assert.AreEqual(viewModel.Child.Value.PropertyChangeCounter, factory.PropertyChangeCounter);
        }

        [TestMethod]
        public void ModelFactoryTests_WithCreateModelOverload()
        {
            IModelFactory factory = new ModelFactory();
            ModelA vm = factory.CreateModel(typeof(ModelC<String>)) as ModelA;

            ModelForFactoryTesting viewModel = factory.CreateModel<ModelForFactoryTesting>();
            Assert.AreEqual(viewModel.ParentFactory, factory);
            Assert.AreEqual(viewModel.PropertyChangeCounter, factory.PropertyChangeCounter);
            Assert.AreEqual(viewModel.Child.Value.ParentFactory, factory);
            Assert.AreEqual(viewModel.Child.Value.PropertyChangeCounter, factory.PropertyChangeCounter);
        }

        [TestMethod]
        public void ModelCommitTest()
        {
            ModelForPropertyTesting viewModel = Creator.Create<ModelForPropertyTesting>();

            viewModel.Int32Property.Value = 34;
            viewModel.ChildCollectionProperty1.Add(Creator.Create<ModelWithNoProperties>());
            viewModel.ChildCollectionProperty1.Add(Creator.Create<ModelWithNoProperties>(), true);
            viewModel.ChildCollectionProperty1.RemoveAt(1);

            Assert.IsTrue(viewModel.IsDirty);
            Assert.IsTrue(viewModel.Int32Property.IsDirty);
            Assert.IsTrue(viewModel.ChildCollectionProperty1.IsDirty);

            viewModel.Commit();

            Assert.IsFalse(viewModel.IsDirty);
            Assert.IsFalse(viewModel.Int32Property.IsDirty);
            Assert.IsFalse(viewModel.ChildCollectionProperty1.IsDirty);
        }

        [TestMethod]
        public void ModelResetTest()
        {
            ModelForPropertyTesting viewModel = Creator.Create<ModelForPropertyTesting>();

            viewModel.Int32Property.Value = 34;
            viewModel.StringProperty6.Value = "SomeValue";
            viewModel.ChildCollectionProperty1.Add(Creator.Create<ModelWithNoProperties>());
            viewModel.ChildCollectionProperty1.Add(Creator.Create<ModelWithNoProperties>(), true);
            viewModel.ChildCollectionProperty1.RemoveAt(1);

            Assert.IsTrue(viewModel.IsDirty);
            Assert.IsTrue(viewModel.Int32Property.IsDirty);
            Assert.IsTrue(viewModel.StringProperty6.IsDirty);
            Assert.IsTrue(viewModel.ChildCollectionProperty1.IsDirty);

            viewModel.Reset();

            Assert.IsTrue(viewModel.IsDirty);
            Assert.IsFalse(viewModel.Int32Property.IsDirty);
            Assert.IsTrue(viewModel.StringProperty6.IsDirty);
            Assert.IsFalse(viewModel.ChildCollectionProperty1.IsDirty);

            viewModel.StringProperty6.Reset();

            Assert.IsFalse(viewModel.IsDirty);
            Assert.IsFalse(viewModel.StringProperty6.IsDirty);
        }

        [TestMethod]
        public void ModelInitializeTest()
        {
            ModelForInitializeTesting viewModel = Creator.Create<ModelForInitializeTesting>();
            Assert.IsTrue(viewModel.InitializeMethodCalled);
        }

        [TestMethod]
        public void ModelReleaseObserversTest()
        {
            ModelForPropertyTesting viewModel = Creator.Create<ModelForPropertyTesting>();
            Boolean propertyChangedCalled = false;
            Boolean canExecuteChanged = false;

            PropertyChangedEventHandler propertyChanged = delegate(Object sender, PropertyChangedEventArgs args)
            {
                propertyChangedCalled = true;
            };

            viewModel.PropertyChanged += propertyChanged;
            viewModel.Int32Property.PropertyChanged += propertyChanged;
            viewModel.StringProperty.PropertyChanged += propertyChanged;
            viewModel.Int32Command.PropertyChanged += propertyChanged;
            viewModel.Int32Command.CanExecuteChanged += (p1, p2) => { canExecuteChanged = true; };
            viewModel.CharCommand.PropertyChanged += propertyChanged;
            viewModel.CharCommand.CanExecuteChanged += (p1, p2) => { canExecuteChanged = true; };
            viewModel.Int32Property.Value = 23;
            viewModel.StringProperty.Value = "This Value";
            viewModel.Int32Command.RaiseCanExecuteChanged();
            viewModel.Int32Command.CanExecute(0);
            viewModel.CharCommand.RaiseCanExecuteChanged();
            viewModel.CharCommand.CanExecute('A');

            Assert.IsTrue(propertyChangedCalled);
            Assert.IsTrue(canExecuteChanged);
            Assert.IsTrue(viewModel.IsDirty);
            propertyChangedCalled = false;
            canExecuteChanged = false;

            viewModel.ReleaseObservers();
            
            viewModel.Reset();
            viewModel.Int32Command.RaiseCanExecuteChanged();
            viewModel.Int32Command.CanExecuteResultOverride = !viewModel.Int32Command.CanExecute(0);
            viewModel.Int32Command.CanExecute(0);
            viewModel.CharCommand.RaiseCanExecuteChanged();
            viewModel.CharCommand.CanExecuteResultOverride = !viewModel.CharCommand.CanExecute('A');
            viewModel.CharCommand.CanExecute('A');

            Assert.IsFalse(propertyChangedCalled);
            Assert.IsFalse(canExecuteChanged);
            Assert.IsFalse(viewModel.IsDirty);
        }

        [TestMethod]
        public void ModelAssignDefaultValuesTest()
        {
            ModelForPropertyTesting viewModel = Creator.Create<ModelForPropertyTesting>();

            viewModel.Int32Property.Value = 34;
            viewModel.StringProperty.Value = "The Value";
            viewModel.ChildCollectionProperty1.Add(Creator.Create<ModelWithNoProperties>());
            viewModel.Commit();

            viewModel.AssignDefaultValues();

            Assert.IsTrue(viewModel.IsDirty);
            Assert.AreEqual(viewModel.Int32Property.Value, viewModel.Int32Property.GetDefaultValue());
            Assert.AreEqual(viewModel.StringProperty.Value, viewModel.StringProperty.GetDefaultValue());
            Assert.AreEqual(viewModel.ChildCollectionProperty1.Count, 0);
        }

        [TestMethod]
        public void ModelValidateTest()
        {
            ModelForPropertyTesting viewModel = Creator.Create<ModelForPropertyTesting>();
            IDictionary<ModelPropertyBase, IEnumerable<ValidationError>> viewModelValidationErrors = viewModel.Validate();
            Assert.IsFalse(viewModel.IsInvalid);
            Assert.AreEqual(viewModelValidationErrors.Count(), 0);

            viewModel.ChildCollectionProperty1.ValidationMethod = delegate(ModelCollection<ModelWithNoProperties> property, ref IEnumerable<ValidationError> errors, ref Boolean isValid)
            {
                List<ValidationError> validationErrors = new List<ValidationError>();
                validationErrors.Add(new ValidationError());
                validationErrors.Add(new ValidationError());
                errors = validationErrors;
                isValid = false;
            };

            viewModel.StringProperty.ValidationMethod = delegate(ModelProperty<String> property, ref IEnumerable<ValidationError> errors, ref Boolean isValid)
            {
                List<ValidationError> validationErrors = new List<ValidationError>();
                validationErrors.Add(new ValidationError());
                errors = validationErrors;
                isValid = false;
            };

            viewModel.Int32Property.ValidationMethod = delegate(ModelProperty<Int32> property, ref IEnumerable<ValidationError> errors, ref Boolean isValid)
            {
                List<ValidationError> validationErrors = new List<ValidationError>();
                validationErrors.Add(new ValidationError());
                errors = validationErrors;
                isValid = true;
            };

            viewModel.ChildCollectionProperty2.ValidationMethod = delegate(ModelCollection<ModelWithNoProperties> property, ref IEnumerable<ValidationError> errors, ref Boolean isValid)
            {
                isValid = false;
            };

            viewModelValidationErrors = viewModel.Validate();

            Assert.IsTrue(viewModel.IsInvalid);
            Assert.AreEqual(viewModelValidationErrors.Count(), 3);
            Assert.AreEqual(viewModelValidationErrors.ElementAt(0).Key, viewModel.StringProperty);
            Assert.AreEqual(viewModelValidationErrors.ElementAt(0).Value.Count(), 1);
            Assert.AreEqual(viewModelValidationErrors.ElementAt(1).Key, viewModel.ChildCollectionProperty1);
            Assert.AreEqual(viewModelValidationErrors.ElementAt(1).Value.Count(), 2);
            Assert.AreEqual(viewModelValidationErrors.ElementAt(2).Key, viewModel.ChildCollectionProperty2);
            Assert.AreEqual(viewModelValidationErrors.ElementAt(2).Value.Count(), 0);

            viewModel.StringProperty.ValidationMethod = null;
            viewModel.ChildCollectionProperty1.ValidationMethod = null;
            viewModel.ChildCollectionProperty2.ValidationMethod = null;

            viewModelValidationErrors = viewModel.Validate();

            Assert.IsFalse(viewModel.IsInvalid);
            Assert.AreEqual(viewModelValidationErrors.Count(), 0);

            viewModel.NullableEnumProperty4.ValidationMethod = delegate(ModelProperty<Nullable<EnumForTesting>> property, ref IEnumerable<ValidationError> errors, ref Boolean isValid)
            {
                isValid = !property.IsDirty;
            };

            viewModel.NullableEnumProperty4.Value = EnumForTesting.TestValue2;
            Assert.IsFalse(viewModel.NullableEnumProperty4.IsInvalid);
            Assert.IsFalse(viewModel.IsInvalid);

            viewModel.ChildCollectionProperty4.ValidationMethod = delegate(ModelCollection<ModelWithNoProperties> property, ref IEnumerable<ValidationError> errors, ref Boolean isValid)
            {
                isValid = property.Count == 0;
            };

            viewModel.ChildCollectionProperty4.Add(Creator.Create<ModelWithNoProperties>());
            Assert.IsTrue(viewModel.ChildCollectionProperty4.IsInvalid);
            Assert.IsFalse(viewModel.IsInvalid);
        }

        [TestMethod]
        public void ModelChildSequenceTests()
        {
            ModelForChildVMSequenceTest viewModelA = Creator.Create<ModelForChildVMSequenceTest>();
            ModelForChildVMSequenceTest viewModelB = Creator.Create<ModelForChildVMSequenceTest>();
            ModelForChildVMSequenceTest viewModelC = Creator.Create<ModelForChildVMSequenceTest>();
            ModelForChildVMSequenceTest viewModelD = Creator.Create<ModelForChildVMSequenceTest>();
            ModelForChildVMSequenceTest viewModelE = Creator.Create<ModelForChildVMSequenceTest>();
            Dictionary<String, Int32> propertyChangeCounts = new Dictionary<String, Int32>();

            viewModelA.Name = "ModelA";
            viewModelB.Name = "ModelB";
            viewModelC.Name = "ModelC";
            viewModelD.Name = "ModelD";
            viewModelE.Name = "ModelE";

            viewModelA.OverrideCallCounts = propertyChangeCounts;
            viewModelB.OverrideCallCounts = propertyChangeCounts;
            viewModelC.OverrideCallCounts = propertyChangeCounts;
            viewModelD.OverrideCallCounts = propertyChangeCounts;
            viewModelE.OverrideCallCounts = propertyChangeCounts;

            viewModelA.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelA__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelAStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelAChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelAChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelAChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelB__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelBStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelBChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelBChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelBChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelC__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelCStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelCChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelCChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelCChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelD__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelDStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelDChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelDChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelDChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelE__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelEStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelEChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelEChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelEChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.ChildProperty.Value = viewModelB;
            viewModelA.ChildProperty.Commit();
            viewModelB.ChildProperty1.Value = null;
            viewModelB.ChildProperty1.Commit();
            viewModelB.ChildProperty1.Validate();
            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            propertyChangeCounts.Clear();

            PerformSequenceTests
            (
                viewModelB.ChildProperty1,
                propertyChangeCounts,
                viewModelC,
                viewModelD,
                viewModelE
            );

            viewModelC.StringProperty.Value = "SomeOtherValue1";
            viewModelD.StringProperty.Value = "SomeOtherValue2";
            viewModelE.StringProperty.Value = "SomeOtherValue3";
            viewModelB.ChildProperty1.Value = null;
            viewModelB.ChildProperty1.Commit();
            viewModelB.ChildProperty1.Validate();
            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            propertyChangeCounts.Clear();

            PerformSequenceTests
            (
                viewModelB.ChildProperty1,
                propertyChangeCounts,
                viewModelC,
                viewModelD,
                viewModelE
            );

            viewModelD.StringProperty.Reset();
            viewModelB.ChildProperty1.Value = null;
            viewModelB.ChildProperty1.Commit();
            viewModelB.ChildProperty1.Validate();
            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            propertyChangeCounts.Clear();

            PerformSequenceTests
            (
                viewModelB.ChildProperty1,
                propertyChangeCounts,
                viewModelC,
                viewModelD,
                viewModelE
            );

            viewModelD.StringProperty.Value = "Value1";
            viewModelE.StringProperty.Reset();
            viewModelB.ChildProperty1.Value = null;
            viewModelB.ChildProperty1.Commit();
            viewModelB.ChildProperty1.Validate();
            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            propertyChangeCounts.Clear();

            PerformSequenceTests
            (
                viewModelB.ChildProperty1,
                propertyChangeCounts,
                viewModelC,
                viewModelD,
                viewModelE
            );
        }

        [TestMethod]
        public void ModelChildSequenceTests2()
        {
            ModelForChildVMSequenceTest viewModelA = Creator.Create<ModelForChildVMSequenceTest>();
            ModelForChildVMSequenceTest viewModelB = new ModelForChildVMSequenceTest(true);
            viewModelB.Initialize(true, null, null);
            ModelForChildVMSequenceTest viewModelC = Creator.Create<ModelForChildVMSequenceTest>();
            ModelForChildVMSequenceTest viewModelD = Creator.Create<ModelForChildVMSequenceTest>();
            ModelForChildVMSequenceTest viewModelE = Creator.Create<ModelForChildVMSequenceTest>();
            Dictionary<String, Int32> propertyChangeCounts = new Dictionary<String, Int32>();

            viewModelA.Name = "ModelA";
            viewModelB.Name = "ModelB";
            viewModelC.Name = "ModelC";
            viewModelD.Name = "ModelD";
            viewModelE.Name = "ModelE";

            viewModelA.OverrideCallCounts = propertyChangeCounts;
            viewModelB.OverrideCallCounts = propertyChangeCounts;
            viewModelC.OverrideCallCounts = propertyChangeCounts;
            viewModelD.OverrideCallCounts = propertyChangeCounts;
            viewModelE.OverrideCallCounts = propertyChangeCounts;

            viewModelA.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelA__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelAStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelAChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelAChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelAChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelB__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelBStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelBChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelBChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelB.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelBChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelC__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelCStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelCChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelCChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelC.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelCChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelD__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelDStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelDChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelDChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelD.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelDChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelE__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.StringProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelEStringProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.ChildProperty.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelEChildProperty__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.ChildProperty1.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelEChild1Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelE.ChildProperty2.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs args)
            {
                ModelForChildVMSequenceTest.AddEventToDictionary(propertyChangeCounts, "ModelEChild2Property__" + args.PropertyName, ModelForChildVMSequenceTest.PropertyChangeCount++);
            };

            viewModelA.ChildProperty.Value = viewModelB;
            viewModelA.ChildProperty.Commit();
            viewModelB.ChildProperty1.Value = null;
            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            propertyChangeCounts.Clear();

            PerformSequenceTests
            (
                viewModelB.ChildProperty1,
                propertyChangeCounts,
                viewModelC,
                viewModelD,
                viewModelE
            );

            viewModelC.StringProperty.Value = "SomeOtherValue1";
            viewModelD.StringProperty.Value = "SomeOtherValue2";
            viewModelE.StringProperty.Value = "SomeOtherValue3";
            viewModelB.ChildProperty1.Value = null;
            viewModelB.ChildProperty1.Commit();
            viewModelB.ChildProperty1.Validate();
            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            propertyChangeCounts.Clear();

            PerformSequenceTests
            (
                viewModelB.ChildProperty1,
                propertyChangeCounts,
                viewModelC,
                viewModelD,
                viewModelE
            );

            viewModelD.StringProperty.Reset();
            viewModelB.ChildProperty1.Value = null;
            viewModelB.ChildProperty1.Commit();
            viewModelB.ChildProperty1.Validate();
            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            propertyChangeCounts.Clear();

            PerformSequenceTests
            (
                viewModelB.ChildProperty1,
                propertyChangeCounts,
                viewModelC,
                viewModelD,
                viewModelE
            );

            viewModelD.StringProperty.Value = "Value1";
            viewModelE.StringProperty.Reset();
            viewModelB.ChildProperty1.Value = null;
            viewModelB.ChildProperty1.Commit();
            viewModelB.ChildProperty1.Validate();
            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            propertyChangeCounts.Clear();

            PerformSequenceTests
            (
                viewModelB.ChildProperty1,
                propertyChangeCounts,
                viewModelC,
                viewModelD,
                viewModelE
            );
        }

        [TestMethod]
        public void ModelWithNoProperties()
        {
            ModelWithNoProperties viewModel = Creator.Create<ModelWithNoProperties>();

            viewModel.Commit();
            viewModel.Reset();

            IDictionary<ModelPropertyBase, IEnumerable<ValidationError>> errors = viewModel.Validate();
            Assert.IsNotNull(errors);
            Assert.AreEqual(errors.Count, 0);

            HashSet<Type> knownTypes = new HashSet<Type>();
            ModelMetadata metadata = viewModel.GetMetadata();
            Assert.IsNotNull(metadata);
            AssertModelMetadataState(viewModel, metadata);
            metadata = viewModel.GetMetadata();
            Assert.IsNotNull(metadata);
            AssertModelMetadataState(viewModel, metadata);

            viewModel.ReleaseObservers();
            viewModel.ReleaseObservers();
        }

        [TestMethod]
        public void ModelPropertyTests()
        {
            RecurseThroughPropertyBehaviorCombinations
            (
                (behavior) =>
                {
                    ModelForRegisteringComputedProperties<Int32> valueTypeModel = new ModelForRegisteringComputedProperties<Int32>(behavior);
                    ModelPropertyTests(valueTypeModel.Property, -1, 3);

                    ModelForRegisteringComputedProperties<Nullable<Decimal>> nullableValueTypeModel = new ModelForRegisteringComputedProperties<Nullable<Decimal>>(behavior);
                    ModelPropertyTests(nullableValueTypeModel.Property, .004M, -.49M);

                    ModelForRegisteringComputedProperties<EnumForTesting> enumModel = new ModelForRegisteringComputedProperties<EnumForTesting>(behavior);
                    ModelPropertyTests(enumModel.Property, EnumForTesting.TestValue2, EnumForTesting.TestValue4);

                    ModelForRegisteringComputedProperties<Nullable<EnumForTesting>> nullableEnumModel = new ModelForRegisteringComputedProperties<Nullable<EnumForTesting>>(behavior);
                    ModelPropertyTests(nullableEnumModel.Property, EnumForTesting.TestValue3, EnumForTesting.TestValue5);

                    ModelForRegisteringComputedProperties<String> stringModel = new ModelForRegisteringComputedProperties<String>(behavior);
                    ModelPropertyTests(stringModel.Property, "Value1", "Value2");

                    ModelForRegisteringComputedProperties<ModelWithNoProperties> childModel = new ModelForRegisteringComputedProperties<ModelWithNoProperties>(behavior);
                    ModelPropertyTests(childModel.Property, Creator.Create<ModelWithNoProperties>(), Creator.Create<ModelWithNoProperties>());

                    ModelForRegisteringComputedProperties<Object> unconstrainedModel = new ModelForRegisteringComputedProperties<Object>(behavior);
                    ModelPropertyTests(unconstrainedModel.Property, Creator.Create<ModelWithNoProperties>(), Creator.Create<ModelWithNoProperties>());

                    ModelForRegisteringComputedProperties<Int32> computedModel = new ModelForRegisteringComputedProperties<Int32>
                    (
                        behavior,
                        delegate(IEnumerable<ModelProperty<Int32>> properties, Int32 currentValue)
                        {
                            Int32 returnValue = 0;

                            foreach (ModelProperty<Int32> property in properties)
                            {
                                returnValue += property.Value;
                            }

                            return returnValue;
                        },
                        true
                    );
                    ModelPropertyTests(computedModel.Property, 45, 55);
                }
            );
        }

        [TestMethod]
        public void PropertyMetadataTest()
        {
            ModelForMetadataTestingC viewModel = Creator.Create<ModelForMetadataTestingC>();
            GetPropertyMetadataTests(viewModel.StringProperty, "StringValue", "StringValue2");
            GetPropertyMetadataTests(viewModel.IntProperty, 12, -30);
            GetPropertyMetadataTests(viewModel.NullableIntProperty, 34, -32);
            GetPropertyMetadataTests(viewModel.EnumProperty, EnumForTesting.TestValue2, EnumForTesting.TestValue4);
            GetPropertyMetadataTests(viewModel.NullableEnumForTesting, EnumForTesting.TestValue3, EnumForTesting.TestValue5);
            GetPropertyMetadataTests<ModelForMetadataTestingC>
            (
                viewModel.ChildModel,
                Creator.Create<ModelForMetadataTestingC>(),
                Creator.Create<ModelForMetadataTestingC>()
            );
            GetPropertyMetadataTests(viewModel.ObjectProperty, new Object(), new Object());
            GetPropertyMetadataTests(viewModel.FullNameProperty, "FullName", "AnotherFullName");
            GetPropertyMetadataTests(viewModel.FirstNameProperty, "Jon", "Jane");
            GetPropertyMetadataTests(viewModel.LastNameProperty, "Buck", "Doe");
        }

        private static void GetPropertyMetadataTests<T>(ModelProperty<T> property, T nonDefaultValue, T nonDefaultValue2)
        {
            ModelScalarPropertyMetadata metadata = property.GetMetadata();
            Assert.AreEqual(metadata.Name, property.Name);
            Assert.AreEqual(metadata.IsDirty, property.IsDirty);
            Assert.AreEqual(metadata.IsInvalid, property.IsInvalid);
            Assert.AreEqual(metadata.IsDefault, property.IsDefault);
            Assert.AreEqual(metadata.AllowSync, property.AllowSync);
            Assert.IsNull(metadata.ValidationErrors);
            Assert.AreEqual(metadata.Value, property.Value);
            Assert.IsNull(metadata.CommittedValue);

            property.Value = nonDefaultValue;
            metadata = property.GetMetadata();

            Assert.AreEqual(metadata.Name, property.Name);
            Assert.AreEqual(metadata.IsDirty, property.IsDirty);
            Assert.AreEqual(metadata.IsInvalid, property.IsInvalid);
            Assert.AreEqual(metadata.IsDefault, property.IsDefault);
            Assert.AreEqual(metadata.AllowSync, property.AllowSync);
            Assert.AreEqual(metadata.ValidationErrors.Count(), property.ValidationErrors.Count());

            for (Int32 index = 0; index < property.ValidationErrors.Count(); index++)
            {
                ValidationError error = property.ValidationErrors.ElementAt(index);
                ValidationErrorMetadata errorMetadata = metadata.ValidationErrors.ElementAt(index);

                Assert.AreEqual(error.ErrorCode, errorMetadata.ErrorCode);
                Assert.AreEqual(error.ErrorDescription, errorMetadata.ErrorDescription);
            }

            if (!property.IsModelProperty)
            {
                Assert.AreEqual(metadata.Value, property.Value);
            }
            else
            {
                Assert.IsNotNull(metadata.Value);
                Assert.AreEqual(metadata.Value.GetType(), typeof(ModelMetadata));
            }

            Assert.AreEqual(metadata.CommittedValue, property.GetDefaultValue());

            property.Commit();
            metadata = property.GetMetadata();

            Assert.AreEqual(metadata.Name, property.Name);
            Assert.AreEqual(metadata.IsDirty, property.IsDirty);
            Assert.AreEqual(metadata.IsInvalid, property.IsInvalid);
            Assert.AreEqual(metadata.IsDefault, property.IsDefault);
            Assert.AreEqual(metadata.AllowSync, property.AllowSync);
            Assert.AreEqual(metadata.ValidationErrors.Count(), property.ValidationErrors.Count());

            for (Int32 index = 0; index < property.ValidationErrors.Count(); index++)
            {
                ValidationError error = property.ValidationErrors.ElementAt(index);
                ValidationErrorMetadata errorMetadata = metadata.ValidationErrors.ElementAt(index);

                Assert.AreEqual(error.ErrorCode, errorMetadata.ErrorCode);
                Assert.AreEqual(error.ErrorDescription, errorMetadata.ErrorDescription);
            }

            if (!property.IsModelProperty)
            {
                Assert.AreEqual(metadata.Value, property.Value);
            }
            else
            {
                Assert.IsNotNull(metadata.Value);
                Assert.AreEqual(metadata.Value.GetType(), typeof(ModelMetadata));
            }

            Assert.IsNull(metadata.CommittedValue);

            property.Validate();
            property.AllowSync = false;
            metadata = property.GetMetadata();

            Assert.AreEqual(metadata.Name, property.Name);
            Assert.AreEqual(metadata.IsDirty, property.IsDirty);
            Assert.AreEqual(metadata.IsInvalid, property.IsInvalid);
            Assert.AreEqual(metadata.IsDefault, property.IsDefault);
            Assert.AreEqual(metadata.AllowSync, property.AllowSync);
            Assert.IsNull(metadata.ValidationErrors);

            if (!property.IsModelProperty)
            {
                Assert.AreEqual(metadata.Value, property.Value);
            }
            else
            {
                Assert.IsNotNull(metadata.Value);
                Assert.AreEqual(metadata.Value.GetType(), typeof(ModelMetadata));
            }

            Assert.IsNull(metadata.CommittedValue);

            property.Value = nonDefaultValue2;
            metadata = property.GetMetadata();

            Assert.AreEqual(metadata.Name, property.Name);
            Assert.AreEqual(metadata.IsDirty, property.IsDirty);
            Assert.AreEqual(metadata.IsInvalid, property.IsInvalid);
            Assert.AreEqual(metadata.IsDefault, property.IsDefault);
            Assert.AreEqual(metadata.AllowSync, property.AllowSync);
            Assert.AreEqual(metadata.ValidationErrors.Count(), property.ValidationErrors.Count());

            for (Int32 index = 0; index < property.ValidationErrors.Count(); index++)
            {
                ValidationError error = property.ValidationErrors.ElementAt(index);
                ValidationErrorMetadata errorMetadata = metadata.ValidationErrors.ElementAt(index);

                Assert.AreEqual(error.ErrorCode, errorMetadata.ErrorCode);
                Assert.AreEqual(error.ErrorDescription, errorMetadata.ErrorDescription);
            }

            if (!property.IsModelProperty)
            {
                Assert.AreEqual(metadata.Value, property.Value);
                Assert.AreEqual(metadata.CommittedValue, property.CommittedValue);
            }
            else
            {
                Assert.IsNotNull(metadata.Value);
                Assert.AreEqual(metadata.Value.GetType(), typeof(ModelMetadata));
                Assert.IsNotNull(metadata.CommittedValue);
                Assert.AreEqual(metadata.CommittedValue.GetType(), typeof(ModelMetadata));
                Assert.AreNotEqual(metadata.Value, metadata.CommittedValue);
            }
        }

        private static void ModelPropertyTests<T>(ModelProperty<T> property, T nonDefaultValue1, T nonDefaultValue2)
        {
            AssertPropertyInitialState(property);

            DuplicateValueAssignment(property, property.GetDefaultValue());
            property.ReleaseObservers();
            property.Parent.ReleaseObservers();

            NewValueAssignment(property, nonDefaultValue1);
            property.ReleaseObservers();
            property.Parent.ReleaseObservers();

            DuplicateValueAssignment(property, nonDefaultValue1);
            property.ReleaseObservers();
            property.Parent.ReleaseObservers();

            NewValueAssignment(property, nonDefaultValue2);
            property.ReleaseObservers();
            property.Parent.ReleaseObservers();

            NewValueAssignment(property, nonDefaultValue1);
            property.ReleaseObservers();
            property.Parent.ReleaseObservers();

            NewValueAssignment(property, property.GetDefaultValue());
            property.ReleaseObservers();
            property.Parent.ReleaseObservers();

            DuplicateValueAssignment(property, property.GetDefaultValue());
            property.ReleaseObservers();
            property.Parent.ReleaseObservers();
        }

        private static void AssertPropertyInitialState<T>(ModelProperty<T> property)
        {
            Assert.AreEqual(property.Value, property.GetDefaultValue());
            Assert.AreEqual(property.CommittedValue, property.GetDefaultValue());
            Assert.IsFalse(property.IsDirty);
            Assert.IsTrue(property.IsDefault);
            Assert.IsFalse(property.IsInvalid);
            Assert.IsNull(property.ValidationErrors);
            Assert.AreEqual(property.Value, property.GetDefaultValue());
            Assert.AreEqual(property.CommittedValue, property.GetDefaultValue());
            Assert.IsNull(property.CompareMethod);
            Assert.IsNull(property.ValidationMethod);
        }

        private static void NewValueAssignment<T>(ModelProperty<T> property, T newValue)
        {
            List<String> propertyNotifications = GetPropertyNotifications(property);
            List<String> propertyParentNotifications = GetPropertyParentNotifications(property);

            Boolean wasDirty = property.IsDirty;
            Boolean wasDefaultValue = property.IsDefault;
            property.Value = newValue;

            AssertCorrectParentChangeNotification
            (
                "IsDirty",
                propertyParentNotifications,
                property.CheckSetsParentIsDirty() && ((!wasDirty && property.CheckSetsIsDirty()) || (wasDirty && property.IsDefault))
            );
            AssertCorrectParentChangeNotification("IsDirtyWithin", propertyParentNotifications, false);
            AssertCorrectParentChangeNotification("IsInvalid", propertyParentNotifications, false);
            AssertCorrectParentChangeNotification("IsInvalidWithin", propertyParentNotifications, false);

            AssertCorrectChangeNotification(property, "Value", propertyNotifications, propertyParentNotifications, true);
            AssertCorrectChangeNotification(property, "CommittedValue", propertyNotifications, propertyParentNotifications, false);
            AssertCorrectChangeNotification(property, "IsDirty", propertyNotifications, propertyParentNotifications, (!wasDirty && property.CheckSetsIsDirty()) || (wasDirty && property.IsDefault));
            AssertCorrectChangeNotification(property, "IsDefault", propertyNotifications, propertyParentNotifications, wasDefaultValue || (property.IsDefault && !wasDefaultValue));
            AssertCorrectChangeNotification(property, "IsInvalid", propertyNotifications, propertyParentNotifications, false);
            
            Assert.AreEqual(property.Parent.IsDirty, property.CheckSetsParentIsDirty() && property.CheckSetsIsDirty() && !property.Compare(property.Value, property.GetDefaultValue()));
            Assert.AreEqual(property.Parent.IsInvalid, false);
            Assert.AreEqual(property.Value, newValue);
            Assert.AreEqual(property.CommittedValue, property.GetDefaultValue());
            Assert.AreEqual(property.IsDirty, property.CheckSetsIsDirty() && !property.Compare(property.Value, property.GetDefaultValue()));
            Assert.AreEqual(property.IsDefault, property.Compare(property.Value, property.GetDefaultValue()));
            Assert.IsFalse(property.IsInvalid);
            
            propertyNotifications.Clear();
            propertyParentNotifications.Clear();
        }

        private static void DuplicateValueAssignment<T>(ModelProperty<T> property, T nonDefaultValue1)
        {
            List<String> propertyNotifications = GetPropertyNotifications(property);
            List<String> propertyParentNotifications = GetPropertyParentNotifications(property);

            property.Value = nonDefaultValue1;

            Assert.AreEqual(propertyNotifications.Count, 0);
            Assert.AreEqual(propertyParentNotifications.Count, 0);
        }

        private static List<String> GetPropertyNotifications<T>(ModelProperty<T> property)
        {
            List<String> propertyNotifications = new List<String>();

            property.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e)
            {
                propertyNotifications.Add(e.PropertyName);
            };

            return propertyNotifications;
        }

        private static List<String> GetPropertyParentNotifications<T>(ModelProperty<T> property)
        {
            List<String> propertyParent = new List<String>();

            property.Parent.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e)
            {
                propertyParent.Add(e.PropertyName);
            };

            return propertyParent;
        }

        private static void AssertCorrectParentChangeNotification
        (
            String changedProperty,
            IEnumerable<String> propertyParentChangeNotifications,
            Boolean propertyChangeEventShouldHaveBeenRaised
        )
        {
            if (propertyChangeEventShouldHaveBeenRaised)
            {
                Assert.IsTrue(propertyParentChangeNotifications.Contains(changedProperty));
            }
            else
            {
                Assert.IsFalse(propertyParentChangeNotifications.Contains(changedProperty));
            }
        }

        private static void AssertCorrectChangeNotification
        (
            ModelPropertyBase property,
            String changedProperty,
            IEnumerable<String> propertyNotifications,
            IEnumerable<String> propertyParentChangeNotifications,
            Boolean propertyChangeEventShouldHaveBeenRaised
        )
        {
            String changedPropertyWrappedName = String.Format("{0}{1}", property.Name, changedProperty);

            if (propertyChangeEventShouldHaveBeenRaised)
            {
                Assert.IsTrue(propertyNotifications.Contains(changedProperty));

                if (property.IsWrapped)
                {
                    //Check the property was signaled in the parent and not in the property
                    Assert.IsFalse(propertyNotifications.Contains(changedProperty));
                    Assert.IsTrue(propertyParentChangeNotifications.Contains(changedPropertyWrappedName));
                }
                else
                {
                    //Check the property was signaled in the property and not in the parent
                    Assert.IsTrue(propertyNotifications.Contains(changedProperty));
                    Assert.IsFalse(propertyParentChangeNotifications.Contains(changedPropertyWrappedName));
                }
            }
            else
            {
                Assert.IsFalse(propertyNotifications.Contains(changedProperty));
                Assert.IsFalse(propertyParentChangeNotifications.Contains(changedPropertyWrappedName));
            }
        }

        private static void AssertModelMetadataState(ModelBase viewModel, ModelMetadata metadata)
        {
            //Need to make sure metadata is correct for all properties
        }

        private static void AssertModelDefaultState(ModelBase viewModel, String name)
        {
            Assert.IsFalse(viewModel.IsDirty);
            Assert.IsFalse(viewModel.IsDirtyWithin);
            Assert.IsFalse(viewModel.IsInvalid);
            Assert.IsFalse(viewModel.IsInvalidWithin);
            Assert.AreEqual(viewModel.InvalidPropertyCount, 0);
            Assert.AreEqual(viewModel.DirtyPropertyCount, 0);
            Assert.IsFalse(viewModel.IsAddedToCollection);
            Assert.IsFalse(viewModel.IsCommittedToCollection);
            Assert.IsFalse(viewModel.IsRemovedFromCollection);
            Assert.IsFalse(viewModel.IsRemovedFromParentProperty);
            Assert.AreEqual(viewModel.Name, name);
            Assert.IsNull(viewModel.Parent);
            Assert.IsNull(viewModel.ParentProperty);
            Assert.IsNull(viewModel.PropertyChangeCounter);
            Assert.IsNull(viewModel.ExtensibleData);
        }

        private static void RecurseThroughPropertyBehaviorCombinations(Action<Nullable<ModelPropertyBehaviors>> onBehaviorCombination)
        {
            IEnumerable<ModelPropertyBehaviors> enumValues = Enum.GetValues(typeof(ModelPropertyBehaviors)).OfType<ModelPropertyBehaviors>();
            Int32 enumValueCount = enumValues.Count();

            onBehaviorCombination(null);

            for (Int32 index = 0; index < enumValueCount; index++)
            {
                ModelPropertyBehaviors currentValue = (ModelPropertyBehaviors)enumValues.ElementAt(index);

                RecurseThroughValues
                (
                    enumValues,
                    enumValueCount,
                    index + 1,
                    currentValue,
                    onBehaviorCombination
                );
            }
        }

        private static void RecurseThroughValues
        (
            IEnumerable<ModelPropertyBehaviors> enumValues,
            Int32 enumValueCount,
            Int32 startIndex,
            ModelPropertyBehaviors aggregateValue,
            Action<Nullable<ModelPropertyBehaviors>> onBehaviorCombination
        )
        {
            onBehaviorCombination(aggregateValue);

            for (Int32 index = startIndex; index < enumValueCount; index++)
            {
                ModelPropertyBehaviors currentValue = (ModelPropertyBehaviors)enumValues.ElementAt(index);

                RecurseThroughValues
                (
                    enumValues,
                    enumValueCount,
                    index + 1,
                    aggregateValue | currentValue,
                    onBehaviorCombination
                );
            }
        }

        private static void PerformSequenceTests
        (
            ModelProperty<ModelForChildVMSequenceTest> viewModelProperty,
            IDictionary<String, Int32> propertyChangeCounts,
            ModelForChildVMSequenceTest viewModelC,
            ModelForChildVMSequenceTest viewModelD,
            ModelForChildVMSequenceTest viewModelE
        )
        {
            Int32 currentChangeCount = 0;

            viewModelProperty.Value = viewModelD;
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnBeforeParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__Parent"], currentChangeCount++);

            if (viewModelD.IsDirty || viewModelD.IsDirtyWithin)
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
            }

            Assert.AreEqual(propertyChangeCounts["ModelD__ParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__HasParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsAssignedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnAssignedToProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDefault"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDefaultChanged"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (!viewModelD.IsDirty && !viewModelD.IsDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsInvalidChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__InvalidPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidChanged"], currentChangeCount++);

                if (!viewModelD.IsDirty && !viewModelD.IsDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Value = null;

            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsAssignedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnBeforeParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__HasParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__ParentProperty"], currentChangeCount++);

            if (viewModelD.IsDirty || viewModelD.IsDirtyWithin)
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelD__Parent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDefault"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDefaultChanged"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsInvalidChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__InvalidPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Value = null;
            Assert.AreEqual(propertyChangeCounts.Count, 0);

            viewModelProperty.Value = viewModelD;
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnBeforeParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__Parent"], currentChangeCount++);

            if (viewModelD.IsDirty || viewModelD.IsDirtyWithin)
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
            }

            Assert.AreEqual(propertyChangeCounts["ModelD__ParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__HasParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsAssignedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnAssignedToProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDefault"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDefaultChanged"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (!viewModelD.IsDirty && !viewModelD.IsDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsInvalidChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__InvalidPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidChanged"], currentChangeCount++);

                if (!viewModelD.IsDirty && !viewModelD.IsDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Value = viewModelD;
            Assert.AreEqual(propertyChangeCounts.Count, 0);

            viewModelProperty.Commit();

            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__CommittedValue"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsCommittedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsAssignedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnCommittedToProperty"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (!viewModelD.IsDirty && !viewModelD.IsDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyCommittedValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Value = viewModelE;

            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsRemovedFromParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsCommittedToParentProperty"], currentChangeCount++);

            if ((viewModelD.IsDirty || viewModelD.IsDirtyWithin) && !(viewModelE.IsDirty || viewModelE.IsDirtyWithin))
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);

                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);

                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnMarkedForRemovalFromProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnBeforeParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__Parent"], currentChangeCount++);

            if ((viewModelE.IsDirty || viewModelE.IsDirtyWithin) && !(viewModelD.IsDirty || viewModelD.IsDirtyWithin))
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);

                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);

                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelE__ParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__HasParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsAssignedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnAssignedToProperty"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (viewModelD.IsParentDirtyWithin && !viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin2"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged2"], currentChangeCount++);
                }

                if (!viewModelD.IsParentDirtyWithin && !viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Reset();

            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsAssignedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnBeforeParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__HasParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__ParentProperty"], currentChangeCount++);

            if ((viewModelE.IsDirty || viewModelE.IsDirtyWithin) && !(viewModelD.IsDirty || viewModelD.IsDirtyWithin))
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelE__Parent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsCommittedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsRemovedFromParent"], currentChangeCount++);

            if (!(viewModelE.IsDirty || viewModelE.IsDirtyWithin) && (viewModelD.IsDirty || viewModelD.IsDirtyWithin))
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnCommittedToProperty"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (!viewModelD.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsInvalidChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__InvalidPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidChanged"], currentChangeCount++);

                if (!viewModelD.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Value = viewModelE;

            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsRemovedFromParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsCommittedToParentProperty"], currentChangeCount++);

            if ((viewModelD.IsDirty || viewModelD.IsDirtyWithin) && !(viewModelE.IsDirty || viewModelE.IsDirtyWithin))
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
            }

            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnMarkedForRemovalFromProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnBeforeParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__Parent"], currentChangeCount++);

            if ((viewModelE.IsDirty || viewModelE.IsDirtyWithin) && !(viewModelD.IsDirty || viewModelD.IsDirtyWithin))
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
            }

            Assert.AreEqual(propertyChangeCounts["ModelE__ParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__HasParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsAssignedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnAssignedToProperty"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (!viewModelD.IsParentDirtyWithin && !viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                if (viewModelD.IsParentDirtyWithin && !viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin2"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged2"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsInvalidChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__InvalidPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidChanged"], currentChangeCount++);

                if (!viewModelD.IsParentDirtyWithin && !viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }

                if (viewModelD.IsParentDirtyWithin && !viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin2"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged2"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Commit();

            Assert.AreEqual(propertyChangeCounts["ModelD__IsRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__IsRemovedFromParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnBeforeParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__HasParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__ParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__Parent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelD__OnRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__CommittedValue"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsCommittedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsAssignedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnCommittedToProperty"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (!viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyCommittedValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Value = null;

            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsRemovedFromParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsCommittedToParentProperty"], currentChangeCount++);

            if (viewModelE.IsParentDirtyWithin)
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnMarkedForRemovalFromProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDefault"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDefaultChanged"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin2"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged2"], currentChangeCount++);
                }
                else
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Value = viewModelProperty.CommittedValue;

            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsCommittedToParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsRemovedFromParent"], currentChangeCount++);

            if (viewModelE.IsDirty || viewModelE.IsDirtyWithin)
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);

                if (!viewModelProperty.CheckSetsIsDirty())
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnCommittedToProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDefault"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDefaultChanged"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (!viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsInvalidChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__InvalidPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidChanged"], currentChangeCount++);

                if (!viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Value = null;

            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__Value"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsRemovedFromParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsCommittedToParentProperty"], currentChangeCount++);

            if (viewModelE.IsParentDirtyWithin)
            {
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidWithinChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
            }

            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnMarkedForRemovalFromProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDefault"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDefaultChanged"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);

                if (viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin2"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged2"], currentChangeCount++);
                }
                else
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
                }

                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsInvalidChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__InvalidPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsInvalid"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsInvalidChanged"], currentChangeCount++);

                if (viewModelE.IsParentDirtyWithin)
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin2"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged2"], currentChangeCount++);
                }
                else
                {
                    Assert.AreEqual(propertyChangeCounts["ModelA__IsInvalidWithin"], currentChangeCount++);
                    Assert.AreEqual(propertyChangeCounts["ModelA__OnIsInvalidWithinChanged"], currentChangeCount++);
                }
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Commit();

            Assert.AreEqual(propertyChangeCounts["ModelE__IsRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__IsRemovedFromParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnBeforeParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__HasParent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__ParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__Parent"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnParentPropertyChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelE__OnRemovedFromParentProperty"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__CommittedValue"], currentChangeCount++);

            if (viewModelProperty.CheckSetsIsDirty())
            {
                Assert.AreEqual(propertyChangeCounts["ModelBChild1Property__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__DirtyPropertyCount"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__IsDirty"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelB__OnIsDirtyChanged"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__IsDirtyWithin"], currentChangeCount++);
                Assert.AreEqual(propertyChangeCounts["ModelA__OnIsDirtyWithinChanged"], currentChangeCount++);
            }

            Assert.AreEqual(propertyChangeCounts["ModelB__OnPropertyCommittedValueChanged"], currentChangeCount++);
            Assert.AreEqual(propertyChangeCounts.Count, currentChangeCount);

            ModelForChildVMSequenceTest.PropertyChangeCount = 0;
            currentChangeCount = 0;
            propertyChangeCounts.Clear();
            viewModelProperty.Commit();

            Assert.AreEqual(propertyChangeCounts.Count, 0);
        }
    }
}
